package com.kmxd.ams.core.util;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.google.common.collect.ImmutableSet;
import com.kmxd.ams.core.common.bo.ConditionBO;
import com.kmxd.ams.core.common.constant.ArchConst;
import com.kmxd.ams.core.common.constant.Constants;
import com.kmxd.ams.core.common.constant.ErrorCode;
import com.kmxd.ams.core.common.enums.OperatorEnum;
import com.kmxd.ams.core.exception.BizException;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;

/**
 * 查询类通用方法
 *
 * @author nickbi
 */
@Slf4j
public class QueryUtil {
  private static final Pattern SQL_SYNTAX_PATTERN =
      Pattern.compile(
          "(insert|delete|update|create|drop|truncate|grant|alter|deny|revoke|call|execute|exec"
              + "|declare|show|rename|set).+(into|from|set|table|database|view|index|on|cursor"
              + "|procedure|trigger|for|password|union|;)",
          2);

  public static Boolean isNullQuery(Object value) {
    if (Objects.isNull(value)) {
      return Boolean.FALSE;
    }
    return value.equals(Constants.QUERY_NULL_DATA_PLACEHOLDER)
        || value.equals(NumberUtil.parseInt(Constants.QUERY_NULL_DATA_PLACEHOLDER));
  }

  public static <T> T filterNull(T req, Class<T> clazz) {
    Map<String, Object> reqMap = BeanUtil.beanToMap(req);
    Map<String, Object> filterMap =
        reqMap.entrySet().stream()
            .filter(e -> Objects.nonNull(e.getValue()))
            .filter(e -> !QueryUtil.isNullQuery(e.getValue()))
            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
    return BeanUtil.toBean(filterMap, clazz);
  }

  public static <T> void buildNullQueryWrapper(T req, QueryWrapper<T> query) {
    Map<String, Object> reqMap = BeanUtil.beanToMap(req);
    reqMap.entrySet().stream()
        .filter(e -> QueryUtil.isNullQuery(e.getValue()))
        .forEach(
            entry -> {
              // 设置空查询
              query.isNull(entry.getKey());
            });
  }

  public static String buildSql(List<ConditionBO> conditionList, String tableName) {
    return buildSql(conditionList, tableName, true);
  }

  public static String buildDataSql(String conditionList) {
    if (ObjectUtil.isEmpty(conditionList)) {
      return "";
    }
    List<ConditionBO> list = JSONUtil.toList(conditionList, ConditionBO.class);

    return buildDataConditionSql(list);
  }
  /**
   * 组合检索，创建sql
   *
   * @param conditionList 条件列表
   * @param tableName 表名
   * @param toUnderLine 驼峰变下划线
   * @return sql
   */
  public static String buildSql(
      List<ConditionBO> conditionList, String tableName, Boolean toUnderLine) {
    if (ObjectUtil.isEmpty(conditionList)) {
      return "";
    }
    StringBuilder conditionSql = new StringBuilder();
    // 拼接表名
    if (StrUtil.isNotBlank(tableName)) {
      conditionSql.append("select * from ").append(tableName).append(" where ");
    }
    conditionList.forEach(
        condition -> {
          // 拼接条件
          if (StrUtil.isNotBlank(condition.getLogic())) {
            conditionSql.append(" ").append(condition.getLogic()).append(" ");
          } else {
            // 拼接查询语句
            String key =
                toUnderLine ? StringUtils.camelToUnderline(condition.getKey()) : condition.getKey();
            conditionSql
                .append(" ")
                .append(key)
                .append(" ")
                .append(condition.getOperator().getOp())
                .append(" ")
                .append(
                    ImmutableSet.of(OperatorEnum.LIKE, OperatorEnum.NOT_LIKE)
                            .contains(condition.getOperator())
                        ? "'%" + condition.getValue() + "%'"
                        : condition.getValue())
                .append(" ");
          }
        });
    String sql = conditionSql.toString();
    if (check(sql)) {
      log.info("sql存在注入风险：{}", sql);
      BizException.throwException(ErrorCode.ARCH_CONDITION_ERROR, "查询语句有注入风险");
    }
    return sql;
  }

  public static String buildDataConditionSql(List<ConditionBO> conditionList) {
    Boolean toUnderLine = false;
    if (ObjectUtil.isEmpty(conditionList)) {
      return "";
    }
    StringBuilder conditionSql = new StringBuilder();
    // 拼接表名
    AtomicInteger index = new AtomicInteger(0);
    conditionList.forEach(
        condition -> {

          // 拼接条件
          int currentIndex = index.getAndIncrement();
          if (0 < currentIndex && currentIndex < conditionList.size()) {
            conditionSql
                .append(" ")
                .append(Optional.ofNullable(condition.getLogic()).orElse("and"))
                .append(" ");
          }

          // 拼接查询语句
          String key =
              toUnderLine ? StringUtils.camelToUnderline(condition.getKey()) : condition.getKey();
          conditionSql
              .append(" ")
              .append(key)
              .append(" ")
              .append(condition.getOperator().getOp())
              .append(" ")
              .append(
                  ImmutableSet.of(OperatorEnum.LIKE, OperatorEnum.NOT_LIKE)
                          .contains(condition.getOperator())
                      ? "'%" + condition.getValue() + "%'"
                      : condition.getValue())
              .append(" ");
        });
    String sql = conditionSql.toString();
    if (check(sql)) {
      log.info("sql存在注入风险：{}", sql);
      BizException.throwException(ErrorCode.ARCH_CONDITION_ERROR, "查询语句有注入风险");
    }
    return sql;
  }

  /**
   * 校验sql注入风险
   *
   * @param value sql
   * @return 校验结果
   */
  public static boolean check(String value) {
    Objects.requireNonNull(value);
    return SQL_SYNTAX_PATTERN.matcher(value).find();
  }

  public static void checkSql(String value) {
    Objects.requireNonNull(value);
    boolean flag = SQL_SYNTAX_PATTERN.matcher(value).find();
    if (flag) {
      log.info("sql存在注入风险：{}", value);
      BizException.throwException(ErrorCode.ARCH_CONDITION_ERROR, "查询语句有注入风险");
    }
  }
  /**
   * 拼接关键词查询条件， 按元数据es索引字段进行查询
   *
   * @param keywords 关键词
   * @param fieldList 字段列表
   * @param query 查询条件
   * @param <T>
   */
  public static <T> void buildSearchWrapper(
      String keywords, List<String> fieldList, QueryWrapper<T> query) {
    if (StrUtil.isBlank(keywords)) {
      return;
    }
    List<String> searchWords = StrUtil.split(keywords, ArchConst.KEYWORDS_SPLIT_WORD);
    if (ObjectUtil.isEmpty(searchWords)) {
      return;
    }

    if (ObjectUtil.isEmpty(fieldList)) {
      return;
    }
    query.and(
        wr -> {
          fieldList.forEach(
              field -> {
                searchWords.forEach(
                    words -> {
                      wr.like(field, words).or();
                    });
              });
        });
  }
}
